REST
REST
На практике работы в IT неизбежно придётся столкнуться с архитектурным стилем REST. Про него как минимум спросят - и главное не проколоться, назвав REST протоколом, фреймворком или программой. Нет, это именно архитектурный стиль. Он предполагает использование уже существующих технологий на базе синхронной коммуникации и HTTP, но предлагает определённые правила.
Что такое REST
REST (Representational State Transfer) — это архитектурный стиль проектирования сетевых приложений, предложенный Роем Филдингом в его докторской диссертации 2000 года. REST не является протоколом, стандартом или спецификацией. Это совокупность принципов и ограничений, которые задают правила взаимодействия между клиентом и сервером через гипермедиа.
REST опирается на существующие протоколы и технологии, в первую очередь на HTTP. Он использует их семантику максимально эффективно, превращая HTTP из простого транспортного механизма в полноценную платформу для построения масштабируемых, надёжных и легко понимаемых распределённых систем.
Основная идея REST — представление всего, с чем взаимодействует клиент, в виде ресурсов, доступных по уникальным URL. Каждый ресурс имеет своё состояние, которое можно получить, изменить, создать или удалить с помощью стандартных операций.
Принципы REST
REST базируется на шести ключевых ограничениях, которые вместе формируют его архитектурный стиль:
-
Единообразие интерфейса (Uniform Interface)
Все компоненты системы взаимодействуют через единый и предсказуемый интерфейс. Это достигается за счёт:- Идентификации ресурсов в запросах
- Манипуляции ресурсами через представления
- Самоописываемых сообщений
- Гипермедиа как движущей силы состояния приложения (HATEOAS)
-
Отсутствие состояния (Statelessness)
Каждый запрос от клиента к серверу должен содержать всю необходимую информацию для его обработки. Сервер не хранит контекст предыдущих запросов. Это упрощает масштабирование, повышает надёжность и упрощает кэширование. -
Кэшируемость (Cacheability)
Ответы сервера должны явно указывать, могут ли они быть закэшированы. Это позволяет клиентам и промежуточным узлам (например, CDN) эффективно использовать кэш, снижая нагрузку на сервер и ускоряя взаимодействие. -
Клиент-серверная архитектура
Разделение ответственности между клиентом (представление данных, пользовательский интерфейс) и сервером (хранение данных, бизнес-логика). Это обеспечивает независимость их разработки и эволюции. -
Слоистая система (Layered System)
Клиент не знает, взаимодействует ли он напрямую с сервером или через промежуточные слои (прокси, балансировщики, шлюзы). Это упрощает безопасность, масштабируемость и управление. -
Код по требованию (Code on Demand, опционально)
Сервер может передавать исполняемый код (например, JavaScript), который клиент выполняет. Это единственный необязательный принцип REST.
Схема работы REST
Взаимодействие в REST-системе происходит по следующему циклу:
- Клиент отправляет HTTP-запрос к ресурсу по уникальному URL.
- Запрос содержит метод (GET, POST и т.д.), заголовки и, при необходимости, тело.
- Сервер обрабатывает запрос, применяя соответствующую логику.
- Сервер возвращает HTTP-ответ с кодом статуса, заголовками и, при необходимости, телом.
- Клиент интерпретирует ответ и, при необходимости, следует гиперссылкам из тела ответа (HATEOAS).
Этот цикл полностью stateless: каждый запрос независим, и сервер не хранит сессию клиента.
Инструменты для работы с REST
Postman
Postman — графический инструмент для тестирования и документирования REST API. Он позволяет:
- Отправлять запросы с различными методами, заголовками и телом
- Сохранять коллекции запросов
- Автоматизировать тесты
- Генерировать документацию
- Имитировать серверные ответы (Mock Server)
Swagger (OpenAPI)
Swagger — спецификация для описания REST API в формате OpenAPI. Она позволяет:
- Описывать структуру API в YAML или JSON
- Генерировать интерактивную документацию
- Создавать клиентские SDK автоматически
- Валидировать запросы и ответы
curl
curl — командная утилита для отправки HTTP-запросов из терминала. Примеры:
# Получить список пользователей
curl -X GET https://api.example.com/users
# Создать нового пользователя
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Ivan", "email": "ivan@example.com"}'
# Обновить пользователя
curl -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name": "Ivan Petrov"}'
REST-ресурс
REST-ресурс — это любая информация, которая может быть названа и доступна через URI. Ресурс представляет собой абстракцию реального объекта: пользователя, заказа, статьи, файла и т.д.
Каждый ресурс обладает тремя ключевыми свойствами:
- Идентифицируемость — ресурс имеет уникальный URI, по которому его можно найти. Например:
/users/123,/orders/456/items/789. - Модифицируемость — состояние ресурса можно изменить с помощью стандартных операций (POST, PUT, PATCH, DELETE).
- Чётко определённое состояние — в любой момент времени ресурс находится в конкретном состоянии, которое можно получить через GET-запрос.
Ресурс не обязательно соответствует одной записи в базе данных. Это может быть агрегат, представление или даже вычисляемое значение.
Statelessness
Statelessness означает, что сервер не сохраняет состояние взаимодействия с клиентом между запросами. Каждый запрос должен содержать всю необходимую информацию:
- Аутентификационные данные (токен, API-ключ)
- Параметры фильтрации, пагинации
- Данные для создания или обновления ресурса
Преимущества statelessness:
- Масштабируемость — любой сервер в кластере может обработать любой запрос
- Надёжность — сбой одного сервера не влияет на другие
- Простота — нет необходимости управлять сессиями
- Кэшируемость — ответы можно кэшировать без учёта контекста
Если приложению требуется состояние (например, корзина покупок), оно должно храниться на клиенте (в cookies, localStorage) или в базе данных с привязкой к пользователю.
Доступные операции с веб-сервисами
REST использует стандартные HTTP-методы для выполнения CRUD-операций (Create, Read, Update, Delete):
Получение данных (Read)
GET /users
GET /users/123
- Получает представление ресурса или коллекции
- Не изменяет состояние сервера
- Безопасный и идемпотентный
Создание новых ресурсов (Create)
POST /users
- Создаёт новый ресурс в коллекции
- Возвращает
201 Createdс заголовкомLocation, указывающим на URI нового ресурса - Не идемпотентный: повторный вызов создаёт ещё один ресурс
Обновление ресурсов (Update)
Полное обновление:
PUT /users/123
- Заменяет всё состояние ресурса
- Идемпотентный: повторный вызов даёт тот же результат
Частичное обновление:
PATCH /users/123
- Изменяет только указанные поля
- Может быть неидемпотентным, если операция относительна (например, увеличение счётчика)
Удаление ресурсов (Delete)
DELETE /users/123
- Удаляет ресурс
- Идемпотентный: повторное удаление не вызывает ошибку
HTTP-методы
| Метод | Идемпотентность | Безопасность | Назначение |
|---|---|---|---|
GET | Да | Да | Получение представления ресурса |
POST | Нет | Нет | Создание ресурса или выполнение действия |
PUT | Да | Нет | Полное обновление ресурса |
PATCH | Зависит | Нет | Частичное обновление ресурса |
DELETE | Да | Нет | Удаление ресурса |
HEAD | Да | Да | Получение метаданных без тела |
OPTIONS | Да | Да | Получение поддерживаемых методов |
Идемпотентность — свойство операции, при котором повторный вызов не изменяет результат после первого успешного выполнения.
Безопасность — свойство метода, при котором он не изменяет состояние сервера.
Как определить сервис, метод и HTTP-метод по URL
В правильно спроектированном REST API URL указывает только на ресурс, а не на действие. Метод действия определяется HTTP-глаголом.
Примеры:
-
❌
/getUser?id=123— глагол в URL, метод неочевиден -
✅
GET /users/123— ресурс/users/123, методGET -
❌
/createOrder— глагол в URL -
✅
POST /orders— ресурс/orders, методPOST -
❌
/updateUser?id=123— глагол в URL -
✅
PUT /users/123— ресурс/users/123, методPUT
Правильный REST API читается как предложение: «Выполни [метод] над [ресурсом]».
Семантика HTTP
Статус-коды
Статус-коды — неотъемлемая часть контракта API:
-
2xx— успех200 OK— успешный запрос201 Created— ресурс создан204 No Content— успешный запрос без тела ответа
-
4xx— ошибка клиента400 Bad Request— невалидные данные401 Unauthorized— отсутствует аутентификация403 Forbidden— нет прав доступа404 Not Found— ресурс не найден409 Conflict— конфликт при создании/обновлении422 Unprocessable Entity— семантическая ошибка данных
-
5xx— ошибка сервера500 Internal Server Error— необработанное исключение503 Service Unavailable— сервис временно недоступен
Никогда не возвращайте 200 при ошибке — это нарушает контракт.
Заголовки кэширования
Cache-Control— директивы кэширования (max-age,no-cache,public,private)ETag— идентификатор версии ресурса для условных запросовLast-Modified— дата последнего изменения
Пример условного запроса:
GET /users/123
If-None-Match: "abc123"
Если ресурс не изменился, сервер вернёт 304 Not Modified.
Идемпотентность
Как уже упоминалось, идемпотентность критична для надёжности:
GET,PUT,DELETE— идемпотентныPOST— не идемпотентенPATCH— может быть идемпотентным, если операции абсолютны
Идемпотентные операции можно безопасно повторять при сетевых ошибках.
URL: правила формирования и составляющие
Общие правила
- Используйте существительные, а не глаголы
- Используйте множественное число для коллекций:
/users,/orders - Стройте иерархию ресурсов:
/users/123/orders/456 - Используйте строчные буквы
- Разделяйте слова дефисами (
kebab-case) или без разделителей
Составляющие URL
https://api.example.com/v1/users/123/orders?status=completed&page=1
│ │ │ │ │ │ │
│ │ │ │ │ │ └── Query parameters
│ │ │ │ │ └── Resource ID
│ │ │ │ └── Sub-resource
│ │ │ └── Collection
│ │ └── Version
│ └── Host
└── Protocol
Шаблоны URL
| Операция | URL | Метод |
|---|---|---|
| Список | /users | GET |
| Создание | /users | POST |
| Получение | /users/123 | GET |
| Обновление | /users/123 | PUT/PATCH |
| Удаление | /users/123 | DELETE |
| Подресурс | /users/123/orders | GET/POST |
Пагинация
Пагинация обязательна для коллекций, которые могут содержать много элементов.
Offset-based (классическая)
GET /orders?page=2&size=20
Ответ:
{
"data": [...],
"pagination": {
"page": 2,
"size": 20,
"total": 150,
"pages": 8
}
}
Cursor-based (для больших данных)
GET /orders?cursor=abc123&limit=20
Ответ:
{
"data": [...],
"next_cursor": "def456"
}
Cursor-based устойчива к изменениям в данных во время перелистывания.
Рекомендации
- Устанавливайте
max_size(например, 100) - Возвращайте
400при невалидных параметрах - Документируйте тип пагинации
HATEOAS
HATEOAS (Hypermedia as the Engine of Application State) — принцип, согласно которому клиент переходит от одного состояния к другому, следуя гиперссылкам в ответах сервера.
Пример:
{
"id": 123,
"name": "Ivan",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"update": "/users/123",
"delete": "/users/123"
}
}
Преимущества HATEOAS:
- Клиент не зависит от жёстко закодированных URL
- API становится самодокументируемым
- Упрощается эволюция API
Недостатки:
- Увеличивает объём ответов
- Сложнее реализовать на клиенте
Resource-Oriented Design
Resource-Oriented Design (ROD) — подход к проектированию API, при котором всё строится вокруг ресурсов:
- Определите основные сущности домена
- Спроектируйте иерархию ресурсов
- Назначьте URI каждой сущности
- Определите допустимые операции для каждого ресурса
- Спроектируйте представления ресурсов
Пример иерархии для интернет-магазина:
/users
/users/{userId}
/users/{userId}/orders
/users/{userId}/orders/{orderId}
/users/{userId}/orders/{orderId}/items
/products
/products/{productId}
Версионирование
Версионирование необходимо с самого начала разработки API.
Способы версионирования
-
В URL (рекомендуется)
GET /api/v1/users -
В заголовке
GET /api/users
Accept: application/vnd.myapi.v1+json -
В параметре запроса (не рекомендуется)
GET /api/users?version=1
Рекомендации
- Используйте major-версии:
v1,v2 - Поддерживайте старые версии минимум 6–12 месяцев
- Документируйте roadmap deprecation
Фильтрация, сортировка, поиск
Используйте query-параметры для управления представлением коллекций:
GET /users?role=admin&status=active&sort=name&order=desc&q=ivan
sort— поле для сортировкиorder— направление (asc/desc)q— глобальный поиск- остальные — фильтры по полям
Для сложных фильтров можно использовать специальные синтаксисы:
- OData:
$filter=age gt 18 and status eq 'active' - RSQL:
age>18;status==active
Вложенные ресурсы
Для отношений «один ко многим» используйте вложенность:
GET /users/123/orders
POST /users/123/orders
Не дублируйте функциональность:
/orders— все заказы/users/123/orders— заказы конкретного пользователя
Избегайте чрезмерной вложенности (более 3 уровней). Для глубоких связей используйте фильтрацию:
- ❌
/companies/1/departments/2/employees/3/tasks/4 - ✅
/tasks?employee_id=3
Частичное представление
Позволяет клиенту запрашивать только нужные поля, снижая объём передаваемых данных:
GET /users?fields=id,name,email
Ответ:
[
{"id": 123, "name": "Ivan", "email": "ivan@example.com"}
]
Это особенно полезно для мобильных клиентов и медленных соединений.
Частичное представление (Field Selection)
Частичное представление — это возможность клиента указать, какие именно поля ресурса он хочет получить в ответе. Это снижает объём передаваемых данных, ускоряет обработку и уменьшает нагрузку на сеть.
Пример запроса:
GET /users?fields=id,name,email
Ответ:
[
{
"id": 123,
"name": "Ivan",
"email": "ivan@example.com"
}
]
Преимущества:
- Экономия трафика для мобильных клиентов
- Снижение нагрузки на сервер при сериализации
- Упрощение логики клиента — получает только нужное
Реализация может быть как простой (разбор строки fields), так и сложной (вложенная выборка: fields=name,orders(id,status)).
Версионирование API
Версионирование — механизм управления изменениями в API, позволяющий поддерживать обратную совместимость и избегать поломки клиентов при обновлениях.
Подходы к версионированию
-
В URL (наиболее распространённый)
GET /api/v1/usersПреимущества: простота, видимость, поддержка прокси и CDN.
Недостатки: нарушает принцип «ресурс один — URI один». -
В заголовке
AcceptGET /api/users
Accept: application/vnd.myapi.v1+jsonПреимущества: чистый URI, соответствует REST.
Недостатки: сложность отладки, неудобство в браузере. -
В параметре запроса (не рекомендуется)
GET /api/users?version=1Недостатки: нарушает кэшируемость, усложняет маршрутизацию.
Рекомендации
- Используйте major-версии:
v1,v2 - Не версионируйте мелкие изменения (добавление необязательных полей)
- Поддерживайте старые версии минимум 6–12 месяцев
- Документируйте roadmap deprecation
- Возвращайте заголовок
Deprecation: trueдля устаревших версий
Фильтрация, сортировка, поиск
Эти механизмы позволяют клиенту управлять представлением коллекций без создания отдельных эндпоинтов.
Фильтрация
Передаётся через query-параметры:
GET /users?role=admin&status=active
Для сложных фильтров можно использовать специализированные синтаксисы:
- OData:
$filter=age gt 18 and status eq 'active' - RSQL:
age>18;status==active
Сортировка
Указывается через параметры sort и order:
GET /users?sort=name&order=desc
Можно поддерживать множественную сортировку:
GET /users?sort=role,name&order=asc,desc
Поиск
Глобальный поиск по нескольким полям:
GET /users?q=ivan
Для продвинутого поиска интегрируется Elasticsearch или аналоги.
Вложенные ресурсы
Вложенные ресурсы отражают иерархические отношения между сущностями.
Пример:
GET /users/123/orders # Заказы пользователя 123
POST /users/123/orders # Создать заказ для пользователя 123
GET /orders/456/items # Позиции в заказе 456
Рекомендации
- Используйте вложенность только для отношений «один ко многим»
- Ограничьте глубину вложенности (максимум 3 уровня)
- Для глубоких связей используйте фильтрацию:
- ❌
/companies/1/departments/2/employees/3/tasks/4 - ✅
/tasks?employee_id=3
- ❌
- Дублируйте функциональность на корневом уровне:
/orders— все заказы/users/123/orders— заказы конкретного пользователя
HATEOAS
HATEOAS (Hypermedia as the Engine of Application State) — принцип, согласно которому клиент переходит от одного состояния к другому, следуя гиперссылкам, возвращаемым сервером.
Пример ответа:
{
"id": 123,
"name": "Ivan",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"update": {
"href": "/users/123",
"method": "PUT"
},
"delete": {
"href": "/users/123",
"method": "DELETE"
}
}
}
Преимущества
- Клиент не зависит от жёстко закодированных URL
- API становится самодокументируемым
- Упрощается эволюция API — можно менять структуру URI без поломки клиентов
Недостатки
- Увеличивает объём ответов
- Сложнее реализовать на клиенте
- Требует дополнительной логики генерации ссылок
HATEOAS считается продвинутой чертой REST, но не обязательной для большинства современных API.
Resource-Oriented Design (ROD)
Resource-Oriented Design — методология проектирования API, ориентированная на ресурсы как основные строительные блоки.
Этапы проектирования
-
Определите доменные сущности
Пользователь, заказ, товар, категория. -
Спроектируйте иерархию ресурсов
/users
/users/{userId}
/users/{userId}/orders
/orders/{orderId}
/orders/{orderId}/items -
Назначьте URI каждой сущности
Используйте существительные во множественном числе. -
Определите допустимые операции
Для каждого ресурса — набор разрешённых HTTP-методов. -
Спроектируйте представления
Какие поля включать, как форматировать даты, как обрабатывать вложенные объекты.
ROD обеспечивает единообразие, предсказуемость и соответствие принципам REST.